
// es6 --   http
// 自定义 http 请求函数
/*
    request(assParams:String|Object[,data:String|Object])
    网络请求 基于 uni.request api
    参数字典
    assParams -- String|Object   #required  // 请求路径 或  配置参数
    {
        url: String 
        ,header: Object || [undefined,null,""]
        ,join: Boolean 
        ,loading: Object || Boolean || any
        ,serialize : Boolean
        ,serializeMethods: Array
    }
    data -- String | Object   // 请求参数 
    由于配置全局接口路径  
    需要请求其他url路径或其他请求配置请在 assParams 配置参数
    主意 assParams 为 Object时  默认进行浅合并  url默认拼接全局 api路径
    当 assParams 中 join 为 false  会直接替换
    loading 默认为一个对象 为了常用配置的省略   可以为  一个 boolean值  true 等于默认loading  false隐藏  复杂类型可以设置为对象
    url 必须为完整 api assParams 
    考虑异步性能与复杂性 请注意
    尽量忽略  success fail 回调  请按 promise调用
    方法 api 参考 axios
    新增  serialize
    // 是否对 post put -data ->params 为对象格式时进行json序列化
    serialize : Boolean
    // 序列化 method 方法
    serializeMethods:["POST","PUT"]
*/
/* 
    2021/9/10  更新
    
    1.  在App生命周期上报错的问题 ， 由于此时App处于在打包状态，无法获取当前视图page对象。 修改page获取不到的情况下为 null
    2. 在之前的逻辑中   默认http获取当前路由为page对象参数传入http拦截回调函数中
    ## 问题
    在当前视图中 有些情况需要操作路由栈中  其他路由对象 。 
    在此种情况下 ， 用其他路由对象调用原型的http方法 page默认还是指向当前视图路由  在特殊情况下有可能发生意向不到错误
    尽管在极大多数情况下，用于操作http拦截都会暴露在当前视图窗口。
    ## 修改方案
    page 可能为[   路由实例对象 , null  ]
    考虑多种情况  默认this  取 window对象  之后对后续操作没什么作用  所以改为 null  也好判断。
    逻辑顺序
        优先判断 this是否为 非顶级对象  如果不是 取 this  this为假值时 取null
        this为顶级对象时  判断当前视图实例是否存在  存在则取 当前视图实例  不存在取 null
*/
const comUtils = require("@/utils/common");
const httpConfig = require("./config");


function request(assParams, data = null, method = "GET") {
    /* 调用对象 */
    const thisCaller = this;
    return new Promise((resolve, reject) => {
        //  参数合并
        if (assParams) {

            let defaultOpts = {
                // 接口地址
                //url: "",
                // 请求方法
                method
                // 传送数据
                , data
                // 默认 header 头配置
                , header: {
                    ["content-type"]: "application/x-www-form-urlencoded"
                }
                // 是否进行url参数拼接 默认拼接  |  false  直接替换
                , join: true
                // 是否显示 loading层  Boolean or Object  对象类型设置 loading配置参数--uni.showLoading
                // loading 弹框文字 { title:"loading..." }
                // , loading: true
                // 是否对 post put -data ->params 为对象格式时进行json序列化
                , serialize: true
                // 序列化 method 方法
                , serializeMethods: ["POST", "PUT"]
            };
            // 请求拦截
            if ( httpConfig.httpRequestIntercept && typeof httpConfig.httpRequestIntercept === "function") {
                defaultOpts = httpConfig.httpRequestIntercept( defaultOpts );
            }else{
                throw Error("Request interception must return an object")
            }

            /* 
                默认配置参数合并 ended
            */
            //如果默认配置参数  url已经存在  则取 url  否则取   Api
            const apiPath = defaultOpts.url ||  httpConfig.httpApi;
            // 获取 arg[0] 参数类型
            const optsType = comUtils.toClass(assParams);
            // arg[0]  url参数暂存变量
            let joinUrl;
            // 如果为字符串  则进行路径拼接
            if (optsType === "String") {
                joinUrl = assParams;
                //如果是 对象类型  则进行浅合并
            } else if (optsType === "Object") {
                joinUrl = assParams.url || "";
                delete assParams.url;
                Object.assign(defaultOpts, assParams);
            } else {
                throw new TypeError(" arg[0] params type is must be String or Object");
            }
            /* --  传入参数合并 ended */
            // default url join
            if (defaultOpts.join) {
                defaultOpts.url = apiPath + joinUrl;
            } else {
                defaultOpts.url = joinUrl;
            }
            // arg[1] http-header respones merge
            if (data) {
                defaultOpts.data = data;
            }

            const { url } = defaultOpts;
            if (url && comUtils.trimAll(url)) {
                /* default loading merge */
                let loadingAuto = false;
                const defaultLoading = { title: "加载中..." };
                let loading;
                if (!comUtils.hasOwn(defaultOpts, "loading")) {
                    loading = defaultLoading;
                } else {
                    loading = defaultOpts.loading;
                }
                if (loading) {
                    if (loading === true) {
                        uni.showLoading(defaultLoading);
                        loadingAuto = true;
                    }
                    if (comUtils.toClass(loading) === "Object") {
                        uni.showLoading(loading);
                        loadingAuto = true;
                    }
                }
                // 对 data params 进行JSON字符串序列化
                const { serialize } = defaultOpts;
                if (serialize) {
                    const { method, serializeMethods = [] , data } = defaultOpts;
                    if (serializeMethods.includes(method)
                        && data
                        && comUtils.toClass( data ) === "Object"
                        ) {
                        Object.keys( data )
                            .forEach(key => {
                                const val = data[key];
                                const valType = comUtils.toClass(val);
                                if (valType === "Object" ||
                                    valType === "Array"
                                ) {
                                    data[key] = JSON.stringify(val);
                                }
                            });
                    }
                }
                const url_notic = joinUrl || url;
                uni.request(
                    Object.assign(defaultOpts, {
                        success(res) {
                            if (loadingAuto) {
                                uni.hideLoading({ fail() { return false; } });
                            }
                            /* 根据后台控制器修改 */
                            const { statusCode, errMsg } = res;
                            const statusReg = /^[23]0\d/;
                            if (statusReg.test(statusCode)) {
                                const resData = res.data;
                                const { code } = resData;
                                let statusCode = Number(code);
                                resData.statusCode = statusCode;
                                function _next(data = resData, type = true) {
                                    if (type) {
                                        resolve(data);
                                    } else {
                                        reject(data);
                                    }
                                }
                                // 异常状态码拦截回调
                                if (httpConfig.httpResponseIntercept && typeof httpConfig.httpResponseIntercept === "function") {
                                    let paramsPage;
                                    //考虑性能
                                    // 大多数为  组件或者路由实例调用
                                    if (thisCaller !== self) {
                                        paramsPage = thisCaller || null;
                                    } else {
                                        // 如果是顶级对象
                                        const pagesArr = getCurrentPages();
                                        let curPage = null;
                                        if (pagesArr && pagesArr.length) {
                                            curPage = pagesArr[pagesArr.length - 1];
                                        }
                                        // 并且当前视图路由存在的情况下
                                        if (curPage) {
                                            paramsPage = curPage;
                                        } else {
                                            paramsPage = null;
                                        }
                                    }
                                    httpConfig.httpResponseIntercept(resData, { page: paramsPage, url: url_notic }, _next);
                                } else {
                                    // status success next
                                    _next();
                                }
                            } else {
                                reject(`HTTP Error( code ): [ ${url_notic} ] {statusCode} `);
                            }
                        },
                        fail(error) {
                            if (loadingAuto) {
                                uni.hideLoading({ fail() { return false; } });
                            }
                            reject(`HTTP Error( connect ):[ ${url_notic} ] {error.errMsg || "请求失败"}`);
                        }
                    })
                );
            } else {
                throw new Error("missing necessary parameters or type error of url");
            }

        } else {
            throw new ReferenceError("http params url must be required");
        }
    });
}


/* 
    get(path:String|Object,data:String|Object);
    get 与 post 基于 request方法
    参数类型与request相同
    get  请求
    不做请求方式强制绑定    留待可以扩展其他 post  put delect方式 

*/
function httpGet(path, data) {
    return request(path, data);
}
function httpPost(path, data) {
    return request(path, data, "POST");
}
function httpPut(path, data) {
    return request(path, data, "PUT");
}
function httpDelete(path, data) {
    return request(path, data, "DELETE");
}


module.exports = {
    httpGet,
    httpPost,
    httpPut,
    httpDelete,
}
